﻿using System;
using System.Collections;
using System.Collections.Generic;
using Hopscotch.Domain.Common;

namespace Hopscotch.Presentation.Common.Collections
{

    public abstract class EntityCollection<T, TKey> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
        where T : DomainBase<TKey>
        where TKey : struct
    {
        private struct IndexItem
        {
            internal IndexItem(int index, T item)
            {
                this.Index = index;
                this.Item = item;
            }

            internal int Index { get; set; }
            internal T Item { get; set; }
        }

        private Dictionary<TKey, IndexItem> indexItemByKey;
        private List<T> items;

        public EntityCollection()
        {
            indexItemByKey = new Dictionary<TKey, IndexItem>();
            items = new List<T>();
        }

        public int Count
        {
            get { return items.Count; }
        }

        public bool IsReadOnly
        {
            get { return ((IList)items).IsReadOnly; }
        }

        public bool IsFixedSize
        {
            get { return ((IList)items).IsFixedSize; }
        }


        object ICollection.SyncRoot
        {
            get { return ((IList)items).SyncRoot; }
        }

        bool ICollection.IsSynchronized
        {
            get { return ((ICollection)items).IsSynchronized; }
        }

        public bool ContainsKey(TKey key)
        {
            return indexItemByKey.ContainsKey(key);
        }

        void IList<T>.Insert(int index, T item)
        {
            InsertItem(index, item);
        }

        void IList<T>.RemoveAt(int index)
        {
            RemoveItem(index);
        }        

        int IList.Add(object value)
        {
            var item = value as T;
            if (item != null)
            {
                int index = items.Count;
                this.InsertItem(index, item);

                return index;
            }

            return -1;
        }
      

        bool IList.Contains(object value)
        {
            return this.Contains(value as T);
        }

        void IList.Insert(int index, object value)
        {
            InsertItem(index, value as T);
        }

        int IList.IndexOf(object value)
        {
            return IndexOf(value as T);
        }

        void IList.Remove(object value)
        {
            throw new NotImplementedException();
        }

        void IList.RemoveAt(int index)
        {
            RemoveItem(index);
        }
            

        object IList.this[int index]
        {
            get { return items[index]; }
            set
            {
                var item = value as T;
                if (item != null || (value == null))
                {
                    SetItem(index, item);
                }
            }
        }

        public T this[int index]
        {
            get { return items[index]; }
            set
            {
                SetItem(index, value);
            }
        }

        void ICollection<T>.CopyTo(T[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        void ICollection.CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return items.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return items.GetEnumerator();
        }


        public T this[TKey key]
        {
            get
            {
                return indexItemByKey[key].Item;
            }
            set
            {            
                throw new NotImplementedException();
            }
        }

        protected virtual TKey GetKeyForItem(T item)
        {
            return item.Id;
        }

        public virtual void Remove(TKey key)
        {
            if (indexItemByKey.ContainsKey(key))
            {
                //Remove
                var indexItem = indexItemByKey[key];
                RemoveItem(indexItem.Index);

                indexItemByKey.Remove(key);
            }
        }

        protected virtual void InsertItem(int index, T item)
        {
            var key = GetKeyForItem(item);

            items.Insert(index, item);
            indexItemByKey.Add(key, new IndexItem(index, item));
        }

        protected virtual void RemoveItem(int index)
        {
            var item = this[index];
            var key = GetKeyForItem(item);

            indexItemByKey.Remove(key);
            items.RemoveAt(index);
        }

        protected virtual void SetItem(int index, T item)
        {
            var key = GetKeyForItem(item);
            indexItemByKey[key] = new IndexItem(index, item);

            items[index] = item;
        }
        
        public virtual void Add(T item)
        {
            this.InsertItem(items.Count, item);
        }

        public virtual void Clear()
        {
            this.items.Clear();
            this.indexItemByKey.Clear();
        }

        public bool Contains(T item)
        {
            var key = GetKeyForItem(item);

            return indexItemByKey.ContainsKey(key) &&
                indexItemByKey[key].Item.Equals(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public virtual bool Remove(T item)
        {
            var key = GetKeyForItem(item);
            if (indexItemByKey.ContainsKey(key))
            {
                var indexItem = indexItemByKey[key];

                RemoveItem(indexItem.Index);

                return true;
            }

            return false;
        }


        public int IndexOf(T item)
        {
            if (item != null)
            {
                var key = GetKeyForItem(item);
                if (indexItemByKey.ContainsKey(key))
                {
                    return indexItemByKey[key].Index;
                }
            }

            return -1;
        }
        
    }
}
